﻿using System;
using System.Collections.Generic;
using SlimDX;

namespace Editor
{
    public class Camera
    {
        public enum Axis
        {
            YAxis,
            ZAxis
        }

        public enum RotateType
        {
            AboutCamera,
            AboutTarget
        }

        private Axis m_WorldUpAxis;
        private Vector3 m_Position;
        private Vector3 m_Target;
        private bool m_bLocked;

        public Axis WorldUpAxis
        {
            get { return m_WorldUpAxis; }
            set { m_WorldUpAxis = value; }
        }

        private Vector3 WorldUpVector
        {
            get
            {
                if (WorldUpAxis == Axis.YAxis)
                    return Vector3.UnitY;
                return Vector3.UnitZ;
            }
        }

        private Vector3 AlternateWorldUpVector
        {
            get
            {
                if (WorldUpAxis == Axis.YAxis)
                    return -Vector3.UnitZ;
                return -Vector3.UnitY;
            }
        }

        public Vector3 Position
        {
            get { return m_Position; }
            set
            {
                if (!m_bLocked)
                    m_Position = value;
            }
        }

        public Vector3 Target
        {
            get { return m_Target; }
            set
            {
                if (!m_bLocked)
                    m_Target = value;
            }
        }

        public Matrix Transform
        {
            get
            {
                Vector3 dir = Vector3.Normalize(m_Target - m_Position);
                float dp = Vector3.Dot(dir, WorldUpVector);
                if (Math.Abs(dp) < 0.9999f)
                    return Matrix.LookAtRH(m_Position, m_Target, WorldUpVector);
                else
                    return Matrix.LookAtRH(m_Position, m_Target, AlternateWorldUpVector);
            }
        }

        public bool Locked
        {
            get { return m_bLocked; }
            set { m_bLocked = value; }
        }

        public float CurrentDistance
        {
            get { return (m_Position - m_Target).Length(); }
        }

        public Camera()
        {
        }

        public void Focus(List<Entity> entities)
        {
            Vector3 centrePos = Vector3.Zero;
            foreach (Entity entity in entities)
            {
                Vector3 scale;
                Quaternion rotation;
                Vector3 translation;
                entity.HierarchicalTransform.Decompose(out scale, out rotation, out translation);
                centrePos += translation;
            }
            if (entities.Count > 0)
                centrePos /= entities.Count;

            Vector3 dir = Vector3.Normalize(m_Position - m_Target);
            m_Target = centrePos;
            m_Position = m_Target + (dir * 256.0f);
        }

        public void Zoom(float d)
        {
            float newDistance = CurrentDistance - d;
            if (newDistance < 1.0f)
                newDistance = 1.0f;

            Vector3 dir = Vector3.Normalize(m_Position - m_Target);
            m_Position = m_Target + (dir * newDistance);
        }

        public void Move(Vector3 csOffset)
        {
            csOffset *= Math.Max(1.0f, CurrentDistance / 10.0f);

            Vector3 dir = Vector3.Normalize(m_Target - m_Position);
            float dp = Vector3.Dot(dir, WorldUpVector);
            Vector3 up;
            if (Math.Abs(dp) < 0.9999f)
                up = WorldUpVector;
            else
                up = AlternateWorldUpVector;
            Vector3 right = Vector3.Cross(dir, up);
            right.Normalize();
            up = Vector3.Cross(right, dir);
            up.Normalize();

            Vector3 offset = (dir * csOffset.Z) +
                             (right * csOffset.X) +
                             (up * csOffset.Y);
            m_Position += offset;
            m_Target += offset;
        }

        public void Rotate(RotateType type, float inclination, float azimuth)
        {
            if (m_bLocked)
                return;

            if (type == RotateType.AboutTarget)
            {
                // Camera position moves
                Quaternion rotLocal = Quaternion.RotationYawPitchRoll(azimuth, inclination, 0);
                Quaternion rotWorld = Quaternion.RotationMatrix(Transform);
                Quaternion rotInvWorld = Quaternion.Invert(rotWorld);

                Quaternion rot = Quaternion.Multiply(rotInvWorld, Quaternion.Multiply(rotLocal, rotWorld));

                Vector3 offset = m_Position - m_Target;
                Vector4 rotatedOffset = Vector3.Transform(offset, rot);
                m_Position = m_Target + new Vector3(rotatedOffset.X, rotatedOffset.Y, rotatedOffset.Z);
            }
            else if (type == RotateType.AboutCamera)
            {
                // Target moves
                Quaternion rotLocal = Quaternion.RotationYawPitchRoll(azimuth, inclination, 0);
                Quaternion rotWorld = Quaternion.RotationMatrix(Transform);
                Quaternion rotInvWorld = Quaternion.Invert(rotWorld);

                Quaternion rot = Quaternion.Multiply(rotInvWorld, Quaternion.Multiply(rotLocal, rotWorld));

                Vector3 offset = m_Target - m_Position;
                Vector4 rotatedOffset = Vector3.Transform(offset, rot);
                m_Target = m_Position + new Vector3(rotatedOffset.X, rotatedOffset.Y, rotatedOffset.Z);
            }
        }
    }
}
